<?php
/**
 ***** 扶摇而上、抱守本真 ******
 * @author Dana
 * @Date 2021/7/1
 */

class MessageBoard{
    protected $db;
    protected $form_errors = [];
    protected $inTransaction = false;

    public function __construct()
    {
        set_exception_handler([$this,'logAndDie']);
        $this->db = new PDO('mysql:host=localhost;dbname=test','test','123456');
        $this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
    }

    public function go()
    {
        // $_REQUEST['cmd'] 的值告诉我们要做什么
        $cmd = isset($_REQUEST['cmd']) ? $_REQUEST['cmd'] : 'show';
        switch ($cmd){
            case 'read':  // read one message
                $this->read();
                break;
            case 'post':
                $this->post();
                break;
            case 'save':
                if($this->valid()){ // 保存提交的留言
                    $this->save(); // 则保存
                    $this->show();  // 并显示留言列表
                }else{
                    $this->post(); // 否则，重新显示提交表单
                }
                break;
            case 'show':
            default:
                $this->show();
                break;
        }
    }

    protected function save(){
        $parent_id = isset($_REQUEST['parent_id']) ? intval($_REQUEST['parent_id']) : 0;
        // 确保在处理留言时留言不会改变
        $this->db->beginTransaction();
        $this->inTransaction = true;

        // 这个留言是回应吗 ？
        if($parent_id){
            // 获取父留言的线索，级别和在线索中的位置
            $st = $this->db->prepare('select thread_id,level,thread_pos from message Where id = ?');
            $st->execute([$parent_id]);
            $parent = $st->fetch();

            // 回应的级别比父留言的级别大1
            $level = $parent['level'] + 1;

            /*
             * 这个线索中，有相同父留言的
             * 所有留言中最大的 thread_pos 是什么
             */
            $st = $this->db->prepare('select max(thread_pos) from message where thread_id = ? and parent_id = ?');
            $st->execute([$parent['thread_id'],$parent_id]);
            $thread_pos = $st->fetchColumn(0);

            // 这个父留言还有其他回应吗？
            if($thread_pos){
                // 这个 thread_pos 比目前最大的 thread_pos 大1
                $thread_pos++;
            }else{
                // 这是第一个回应，所以把它放在父留言后面
                $thread_pos = $parent['thread_pos'] + 1;
            }

            /*
             * 将线索中位于这个留言之后的
             * 所有留言的 thread_pos 增1
             */
            $st = $this->db->prepare('update message set thread_pos = thread_pos+1 where thread_id = ? and thread_pos >=?');
            $st->execute([$parent['thread_id'],$thread_pos]);

            // 新留言的线索指定为父留言的 thread_id
            $thread_id = $parent['thread_id'];
        }else{
            // 这个留言不是一个回应，所以这是一个新线索的开始
            $thread_id = $this->db->query('select max(thread_id) +1 from message')->fetchColumn(0);

            // 如果还没有记录行，确保 thread_id 从1开始
            if(!$thread_id){
                $thread_id = 1;
            }
            $level = 0;
            $thread_pos = 0;
        }

        /*将这个留言插入数据库。使用 prepare 和 execute
       确保所有字段都正确地加引号*/
        $st = $this->db->prepare("INSERT INTO message (id,thread_id,parent_id,thread_pos,posted_on,`level`,author,subject,body) values (?,?,?,?,?,?,?,?,?)");

        $st->execute([null,$thread_id,$parent_id,$thread_pos,date('Y-m-d H:i:s'),$level,$_REQUEST['author'],$_REQUEST['subject'],$_REQUEST['body']]);
        $this->db->commit();

        // 提交所有操作
        $this->inTransaction = false;
    }

    protected function show(){
        print '<h2>Message List</h2><p>';

        /*按线索 thread_id 和在线索的位置 thread_pos
        对留言排序*/
        $st = $this->db->query("select id,author,subject,Length(body) as body_length,posted_on,level from message order by thread_id,thread_pos");

        while($row = $st->fetch()){
            // 将级别 > 0 的留言缩进
            print str_repeat('&nbsp;',4*$row['level']);
            // 显示有关留言的信息，并提供一个链接来读取
            $when = date('Y-m-d H:i',strtotime($row['posted_on']));
            print "<a href='".htmlentities($_SERVER['PHP_SELF']).
                "?cmd=read&amp;id={$row['id']}'>".htmlentities($row['subject']).'</a>  by '.
                htmlentities($row['author']). ' @ '.
                htmlentities($when).
                "({$row['body_length']} bytes) <br/>";
        }

        // 提供一种方法提交非回应的留言
        print "<hr/><a href='".htmlentities($_SERVER['PHP_SELF']).
            "?cmd=post'> Start a new Thread</a>";
    }

    /**
     * 显示单个留言
     * @author Zhoufurong
     * @Date 2021/7/1
     */
    public function read()
    {
        //确保传入的留言ID是一个整数，而且确实表示一个留言
        if(!isset($_REQUEST['id'])){
            throw new Exception('No message ID supplied');
        }
        $id = intval($_REQUEST['id']);
        $st = $this->db->prepare('select author,subject,body,posted_on from message where id = ?');
        $st->execute([$id]);
        $msg = $st->fetch();
        if(!$msg){
            throw new Exception('Bad message ID');
        }
        // 不显示面向用户的HTML,而是将换行显示为 HTML 换行符
        $body = nl2br(htmlentities($msg['body']));

        // 显示留言，并提供链接来做回应以及返回留言列表
        $self = htmlentities($_SERVER['PHP_SELF']);
        $subject = htmlentities($msg['subject']);
        $author = htmlentities($msg['author']);
        print<<<_HTML_
<h2>$subject</h2>
<h3>by $author</h3>
<p>$body</p>
<hr/>
<a href="$self?cmd=post&parent_id=$id"> Reply</a>
<hr/>
<a href="$self?cmd=list"> List Message</a>
_HTML_;
    }

    public function post()
    {
        $safe = [];
        foreach (['author','subject','body'] as $field){
            // 转义默认字段值中的字符
            if(isset($_REQUEST[$field])){
                $safe[$field] = htmlentities($_POST[$field]);
            }else{
                $safe[$field] = '';
            }
            // 将错误消息用红色显示
            if(isset($this->form_errors[$field])){
                $this->form_errors[$field] = '<span style="color:red">'.
                    $this->form_errors[$field].'</span><br/>';
            }else{
                $this->form_errors[$field] = '';
            }
        }

        // 这个留言是一个回应吗？
        if(isset($_REQUEST['parent_id']) && $parent_id = intval($_REQUEST['parent_id'])){
            // 提交表单是发送 parent_id
            $parent_field = sprintf('<input type="hidden" name="parent_id" value="%d" />',$parent_id);
            // 如果未传入主题，则使用父留言的主题
            if(!strlen($safe['subject'])){
                $st = $this->db->prepare('select subject from message where id=?');
                $st->execute([$parent_id]);
                $parent_subject = $st->fetchColumn(0);

                // 如果存在父主题且没有 Re：则为父这题前加前缀 Re:
                $safe['subject'] = htmlentities($parent_subject);
                if($parent_subject && (!preg_match('/^re:/i',$parent_subject))){
                    $safe['subject'] = "Re:{$safe['subject']}";

                }
            }
        }else{
            $parent_field = '';
        }
        // 显示提交表单，并提供错误和默认值
        $self = htmlentities($_SERVER['PHP_SELF']);
        print<<<_HTML_
<form method="post" action="$self">
<table>
    <tr>
        <td>Your name:</td>
        <td>{$this->form_errors['author']}  <input type="text" name="author" value="{$safe['author']}" /></td>
    </tr>
    <tr>
        <td>Subject:</td>
        <td>{$this->form_errors['subject']}  <input type="text" name="subject" value="{$safe['subject']}" /></td>
    </tr>
    <tr>
        <td>Message:</td>
        <td>{$this->form_errors['body']} 
             <textarea rows="4" cols="30" wrap="physical" name="body">{$safe['body']}</textarea>
        </td>
    </tr>
    <tr>
       <td colspan="2"> <input type="submit" value="Post Message" /></td>
    </tr>
</table>
$parent_field
<input type="hidden" name="cmd" value="save" />
</form>
_HTML_;
    }

    public function valid()
    {
        $this->form_errors = [];
        if(!isset($_POST['author']) && strlen(trim($_POST['author']))){
            $this->form_errors['author'] = 'Please enter you name.';
        }
        if(!isset($_POST['subject']) && strlen(trim($_POST['subject']))){
            $this->form_errors['subject'] = 'Please enter a message subject.';
        }
        if(!isset($_POST['body']) && strlen(trim($_POST['body']))){
            $this->form_errors['body'] = 'Please enter a message body.';
        }
        return (count($this->form_errors) == 0);
    }

    public function logAndDie(Exception $e)
    {
        print "ERROR: ".htmlentities($e->getMessage());
        if($this->db && $this->db->inTransaction()){
            $this->db->rollBack();
        }
        exit();
    }
}


/*
 * 限制线索深度*/

/*$st = $this->db->query('select * from message where level <=1 order by thread_id,thread_pos');
while($row = $st->fetch()){
    // 显示各个留言
}*/


$board = new MessageBoard();
$board->go();